package com.atguigu.yygh.hosp.service.impl;/*
 * @author: XueYouPeng
 * @time: 2023/6/9 20:13
 */

import com.alibaba.fastjson.JSON;
import com.atguigu.yygh.hosp.repository.DepartmentRepository;
import com.atguigu.yygh.hosp.repository.HospitalRepository;
import com.atguigu.yygh.hosp.repository.ScheduleRepository;
import com.atguigu.yygh.hosp.service.HospitalService;
import com.atguigu.yygh.hosp.service.ScheduleService;
import com.atguigu.yygh.model.hosp.BookingRule;
import com.atguigu.yygh.model.hosp.Department;
import com.atguigu.yygh.model.hosp.Hospital;
import com.atguigu.yygh.model.hosp.Schedule;
import com.atguigu.yygh.vo.hosp.BookingScheduleRuleVo;
import com.atguigu.yygh.vo.hosp.ScheduleOrderVo;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.*;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

@Service
public class ScheduleServiceImpl implements ScheduleService {

    @Autowired
    ScheduleRepository scheduleRepository;

    @Autowired
    MongoTemplate mongoTemplate;

    @Autowired
    HospitalService hospitalService;

    @Autowired
    HospitalRepository hospitalRepository;

    @Autowired
    DepartmentRepository departmentRepository;

    /*
     * 上傳排版
     * @author: XueYouPeng
     * @time: 2023/6/9 20:16
     */
    @Override
    public void saveSchedule(Map<String, Object> paramMap) {
        String hoscode = (String) paramMap.get("hoscode");
        String hosScheduleId = (String) paramMap.get("hosScheduleId");


        //paramMap 转成schedule
        Schedule schedule = JSON.parseObject(JSON.toJSONString(paramMap), Schedule.class);

        //查询是否存在
        Schedule scheduleFromMongo = scheduleRepository.findByHoscodeAndHosScheduleId(hoscode,hosScheduleId);

        if (scheduleFromMongo==null){
            schedule.setCreateTime(new Date());
            schedule.setUpdateTime(new Date());
            scheduleRepository.save(schedule);
        }else {
            schedule.setCreateTime(scheduleFromMongo.getCreateTime());
            schedule.setUpdateTime(new Date());
            //更新要有id
            schedule.setId(scheduleFromMongo.getId());
            scheduleRepository.save(schedule);
        }
    }


    /*
     * 查询排版 分页
     * @author: XueYouPeng
     * @time: 2023/6/9 20:35
     */
    @Override
    public Page<Schedule> list(Map<String, Object> paramMap) {
        //准备条件
        String hoscode = (String) paramMap.get("hoscode");
        String depcode = (String) paramMap.get("depcode");

        //分页参数
        Integer pageNum = Integer.parseInt((String) paramMap.get("page"));
        Integer pageSize = Integer.parseInt((String) paramMap.get("limit"));

        //分页查询的条件
        Schedule schedule = new Schedule();
        schedule.setHoscode(hoscode);
        schedule.setDepcode(depcode);
        Example<Schedule> example = Example.of(schedule);

        //降序 按照创建时间
        Pageable pageable = PageRequest.of(pageNum - 1, pageSize, Sort.by(Sort.Direction.DESC, "creatTime"));

        Page<Schedule> schedulePage = scheduleRepository.findAll(example, pageable);
        return schedulePage;
    }

    /*
     * 删除排班
     * @author: XueYouPeng
     * @time: 2023/6/9 20:46
     */
    @Override
    public void remove(Map<String, Object> paramMap) {
        String hoscode = (String) paramMap.get("hoscode");
        String hosScheduleId = (String)paramMap.get("hosScheduleId");

        Schedule schedule = scheduleRepository.findByHoscodeAndHosScheduleId(hoscode, hosScheduleId);

        if (schedule != null){
            scheduleRepository.deleteById(schedule.getId());
        }
    }

    @Override
    public Map<String, Object> getScheduleRule(Integer page, Integer limit, String hoscode, String depcode) {

        //查询某医院当前科室下的所有的排班
        List<Schedule> list = scheduleRepository.findByHoscodeAndDepcode(hoscode,depcode);

        //根据workDate进行分组 有几个workDate就有几个组
        //k-workDate
        //v-workDate相同的一组排班
        Map<Date, List<Schedule>> map = list.stream().collect(Collectors.groupingBy(Schedule::getWorkDate));

        //得到有几个workDate
        int total = map.size();

        //遍历map，每遍历一次，创建一个BookingScheduleRuleVo对象，对应前端一个小方块数据
        List<BookingScheduleRuleVo> ruleVoList = new ArrayList<>();
        for (Map.Entry<Date,List<Schedule>> entry: map.entrySet()){
            //某一个排班
            Date wordData = entry.getKey();
            //workDate相同的一组排班
            List<Schedule> value = entry.getValue();

            //每一个方块对应一个ruleVo对象
            BookingScheduleRuleVo ruleVo = new BookingScheduleRuleVo();

            //ruleVo属性赋值
            ruleVo.setWorkDate(value.get(0).getWorkDate());
            ruleVo.setWorkDateMd(value.get(0).getWorkDate());
            //该科室在该日期下的排班数量（医生数量）
            ruleVo.setDocCount(value.size());
            //该科室下，该日期下的排班列表统计总的号源数量和剩余的总号源数量
            //value集合中的availableNumber 求和，赋值给ruleVo中的setAvailableNumber
            //value集合中的reservedNumber 求和，赋值给ruleVo中的setReservedNumber
            ruleVo.setReservedNumber(this.sumReservedNumber(value));
            ruleVo.setAvailableNumber(this.sumAvailableNumber(value));

            //星期

            ruleVo.setDayOfWeek(this.getDayOfWeek(wordData));

            ruleVoList.add(ruleVo);
        }
        //集合截取分页
        int start = (page - 1) * limit;             // 0  5  10
        int end = (page - 1) * limit + (limit - 1);// 4  9  14

        //判断end是否越界
        //第三页end=14
        if (ruleVoList.size() < (end + 1)){
            end = ruleVoList.size() - 1; //如果11条数据，索引最多到11-1
        }

        //分页
        List<BookingScheduleRuleVo> pageList = new ArrayList<>();
        for (int i = start; i <= end; i++){
            BookingScheduleRuleVo ruleVo = ruleVoList.get(i);
            pageList.add(ruleVo);
        }

        HashMap<String, Object> result = new HashMap<>();
        result.put("bookingScheduleRuleVoList",pageList);
        result.put("total",total);

        return result;
    }

    @Override
    public Map<String, Object> getScheduleRule2(Integer page, Integer limit, String hoscode, String depcode) {
        //基于mg中的聚合查询，需要使用MongoTemplate对象

        //针对哪些排班进行分组聚合统计
        Criteria criteria = Criteria.where("hoscode").is(hoscode).and("depcode").is(depcode);
        Aggregation agg = Aggregation.newAggregation(
                Aggregation.match(criteria),
                Aggregation.group("workDate")//针对List<Schedule>按照workDate分组
                        .first("workDate").as("workDate")//按照workDate分组，每一组排班中取出第一个排班的workDate，as表示将该值赋值给BookingScheduleRuleVo中的workDate属性
                        .first("workDate").as("workDateMd")
                        .count().as("docCount") //每一组排班的数量赋值给BookingScheduleRuleVo对象中的docCount属性
                        .sum("reservedNumber").as("reservedNumber")//每一组排班对reservedNumber求和，赋值给BookingScheduleRuleVo中的reservedNumber
                        .sum("availableNumber").as("availableNumber")//每一组排班对reservedNumber求和，赋值给BookingScheduleRuleVo中的reservedNumber
                , // 注意：先排序，再分页
                Aggregation.sort(Sort.by(Sort.Direction.ASC,"workDate"))//list集合，按照workDate升序排序
                ,
                //分页
                Aggregation.skip((page-1)*limit)
                ,//跳过之前多少条
                Aggregation.limit(limit)//每页多少条
        );
        //参数1：Aggregation，用来指定针对哪些数据进行聚合，每一组如何统计（每一组统计哪些数据），以及分页，排序
        //参数2：需要被分组聚合统计的数据Schedule
        //参数3：聚合分组统计完成后，每一组封装成BookingScheduleRuleVo类型的对象
        AggregationResults<BookingScheduleRuleVo> aggregate = mongoTemplate.aggregate(agg, Schedule.class, BookingScheduleRuleVo.class);
        //返回list集合，分组聚合之后，每一组产出一个BookingScheduleRuleVo对象，形成一个list集合
        List<BookingScheduleRuleVo> list = aggregate.getMappedResults();

        //星期
        for (BookingScheduleRuleVo ruleVo : list) {
            ruleVo.setDayOfWeek(this.getDayOfWeek(ruleVo.getWorkDate()));
        }
        HashMap<String, Object> map = new HashMap<>();
        map.put("bookingScheduleRuleVoList",list);
        map.put("total",this.getTotal(hoscode,depcode));

        return map;
    }

    @Override
    public List<Schedule> getScheduleDetail(String hoscode, String depcode, String workDate) {
        DateTime dateTime = new DateTime(workDate);
        Date date = dateTime.toDate();

        List<Schedule> list = scheduleRepository.findByHoscodeAndDepcodeAndWorkDate(hoscode, depcode, date);
        return list;
    }

    @Override
    public Map<String, Object> getBookingScheduleRule(Integer pageNum, Integer pageSize, String hoscode, String depcode) {

        Hospital hospital = hospitalRepository.findByHoscode(hoscode);
        BookingRule bookingRule = hospital.getBookingRule();
        Integer cycle = bookingRule.getCycle();

        //今天是否开始放号 8:30
        String releaseTime = bookingRule.getReleaseTime();
        //今天的8:30转成DateTime
        DateTime dateTime = this.getDateTime(new Date(),releaseTime);
        //f成立，今天已经开始放号了
        if (dateTime.isBeforeNow()){
            cycle++;//总的日期个数=cycle+1
        }

        //根据cycle把所有的日期都创建出来，根据分页参数截取
        List<Date> allDateList = new ArrayList<>();
        for (int i = 0; i < cycle; i++){
            DateTime dt = new DateTime().plusDays(i);//带有时分秒的日期
            //dt转成只表示年月日的Date
            String s = dt.toString("yyyy-MM-dd");
            Date date = new DateTime(s).toDate();
            allDateList.add(date);
        }
        //截取当前页的日期列表
        int begin = (pageNum-1)*pageSize ;        // 0    7    14
        int end = (pageNum-1)*pageSize+pageSize-1;// 6    13   20

        //判断end是否越界
        if (end > cycle){
            end = cycle - 1;
        }
        //从begin开始，截取到end
        List<Date> pageDateList = new ArrayList<>();
        for (int i = begin; i <= end; i++){
            Date date = allDateList.get(i);
            pageDateList.add(date);
        }


        //------------------开始对pageDateList日期范围内的排班进行聚合--------------------------------

        Criteria criteria = Criteria.where("hoscode").is(hoscode).and("depcode").is(depcode)
                .and("workDate").in(pageDateList) ;  //注意： in方法（不是is）   17-23 的日期集合

        Aggregation agg =  Aggregation.newAggregation(
                Aggregation.match(criteria), //条件，针对哪些数据进行聚合
                Aggregation.group("workDate")
                        //每一组提取数据赋值给BookingScheduleRuleVo中的属性
                        .first("workDate").as("workDate")
                        .first("workDate").as("workDateMd")
                        .count().as("docCount")
                        .sum("reservedNumber").as("reservedNumber")
                        .sum("availableNumber").as("availableNumber"),
                Aggregation.sort(Sort.Direction.ASC,"workDate")
        );

        //参数1：聚合对象
        //参数2：指定针对啥数据进行聚合
        //参数3：聚合后，每一组产出的结果
        AggregationResults<BookingScheduleRuleVo> aggregate = mongoTemplate.aggregate(agg, Schedule.class, BookingScheduleRuleVo.class);
        //三个ruleVo（17  18  20 有排班，就有聚合的ruleVo）
        List<BookingScheduleRuleVo> ruleVoList = aggregate.getMappedResults();

        //ruleVoList 转成map ， key-value分别是BookingScheduleRuleVo.getWorkDate()  , BookingScheduleRuleVo
        Map<Date,BookingScheduleRuleVo> map = new HashMap<>();

        map = ruleVoList.stream().collect(Collectors.toMap(
                //集合中的每一个对象的哪个属性作为key（ruleVo.getWorkDate()）
                BookingScheduleRuleVo::getWorkDate,
                //集合中的每一个对象
                vo->vo
        ));

        List<BookingScheduleRuleVo> bookingScheduleRuleVoList = new ArrayList<>();

        //----------------------对于没有ruleVo的日期，创建一个默认的vo---------------------------
        //当前页的日期列表
        for (int i = 0; i < pageDateList.size(); i++){
            Date workDate = pageDateList.get(i);

            //遍历一个日期后，判断该日期是否存在ruleVo，不存在创建默认
            //从ruleVoList中检查是否存在workDate这个日期的对象
            BookingScheduleRuleVo ruleVo = map.get(workDate);

            if (ruleVo==null){
                ruleVo = new BookingScheduleRuleVo();

                ruleVo.setWorkDate(workDate);
                ruleVo.setWorkDateMd(workDate);

                ruleVo.setAvailableNumber(-1);
                ruleVo.setReservedNumber(-1);
                ruleVo.setDocCount(-1);
            }

            //status赋值
            // 统一设置成0
            ruleVo.setStatus(0);
            // 第一页的第一条，需要判断今天是否已经停止放号，如果已经停止放号，status=-1
            if (pageNum==1 && i == 0){
                String stopTime = bookingRule.getStopTime();//11:30
                DateTime stop = this.getDateTime(new Date(), stopTime);
                if (stop.isBeforeNow()){
                    ruleVo.setStatus(-1);
                }
            }

            // 最后一页的 最后一条，status=1
            //总页数
            int pages = cycle / pageSize + (cycle % pageSize==0?0:1);
            if (pageNum == pages && i == pageDateList.size()-1){
                ruleVo.setStatus(1);
            }

            //dayOfWeek赋值
            ruleVo.setDayOfWeek(this.getDayOfWeek(ruleVo.getWorkDate()));

            bookingScheduleRuleVoList.add(ruleVo);

        }


        HashMap<String, Object> result = new HashMap<>();
        result.put("bookingScheduleList",bookingScheduleRuleVoList);
        result.put("total",cycle);

        HashMap<String, Object> baseMap = new HashMap<>();
        baseMap.put("hosname",hospital.getHosname());

        Department department = departmentRepository.findByHoscodeAndDepcode(hoscode, depcode);
        baseMap.put("bigname",department.getBigname());
        baseMap.put("depname",department.getDepname());
        baseMap.put("workDateString",new DateTime().toString("yyyy年MM月"));//2023年06月
        baseMap.put("releaseTime",bookingRule.getReleaseTime());//放号时间

        result.put("baseMap",baseMap);

        return result;
    }

    @Override
    public Schedule getScheduleById(String scheduleId) {
        Optional<Schedule> byId = scheduleRepository.findById(scheduleId);
        Schedule schedule = byId.get();

        this.packSchedule(schedule);

        return schedule;
    }

    @Override
    public ScheduleOrderVo getScheduleOrderVo(String scheduleId) {
        //根据排班id查询排班对象
        Optional<Schedule> byId = scheduleRepository.findById(scheduleId);
        Schedule schedule = byId.get();

        ////根据医院编号，查询医院对象
        Hospital hospital = hospitalRepository.findByHoscode(schedule.getHoscode());

        //查询科室
        Department department = departmentRepository.findByHoscodeAndDepcode(schedule.getHoscode(), schedule.getDepcode());

        //创建ScheduleOrderVo
        ScheduleOrderVo scheduleOrderVo = new ScheduleOrderVo();

        //属性赋值
        scheduleOrderVo.setHoscode(schedule.getHoscode());
        scheduleOrderVo.setHosname(hospital.getHosname());
        scheduleOrderVo.setDepcode(schedule.getDepcode());//科室编号
        scheduleOrderVo.setDepname(department.getDepname());//科室名称
        scheduleOrderVo.setHosScheduleId(schedule.getHosScheduleId());//排班编号
        scheduleOrderVo.setTitle(schedule.getTitle());
        scheduleOrderVo.setReserveDate(schedule.getWorkDate());//排班日期
        scheduleOrderVo.setReserveTime(schedule.getWorkTime());//0，1
        scheduleOrderVo.setAvailableNumber(schedule.getAvailableNumber());//剩余可用号源数量
        scheduleOrderVo.setAmount(schedule.getAmount());//挂号费

        //退号时间（截止时间，最晚的退号时间）
        scheduleOrderVo.setQuitTime(this.getQuiteTime(hospital,schedule));

        Date date1 = this.getDateTime(new Date(), hospital.getBookingRule().getReleaseTime()).toDate();
        scheduleOrderVo.setStartTime(date1);// 今天 + 08:30

        Date date3 = new DateTime().plusDays(hospital.getBookingRule().getCycle()).toDate();
        Date date4 = this.getDateTime(date3, hospital.getBookingRule().getStopTime()).toDate();

        scheduleOrderVo.setEndTime(date4);// 挂号结束时间 = 今天延后cycle + stopTime（预约周期内的最后一天的11:30）


        Date date2 = this.getDateTime(new Date(), hospital.getBookingRule().getStopTime()).toDate();
        scheduleOrderVo.setStopTime(date2);//今天 + 11:30

        return scheduleOrderVo;
    }

    private Date getQuiteTime(Hospital hospital, Schedule schedule) {
        Integer quitDay = hospital.getBookingRule().getQuitDay();// -1 就诊人前一天
        String quitTime = hospital.getBookingRule().getQuitTime();//15:30

        //就诊日当天
        DateTime workDate = new DateTime(schedule.getWorkDate());

        //判断就诊日是否是今天
        String today = new DateTime().toString("yyyy-MM-dd");

        //判断今天如果不是就诊日，if成立，退号时间使用默认的规则
        if(!workDate.toString("yyyy-MM-dd").equals(today)){
            //挂号的就诊日不是今天，使用默认规则
            workDate = workDate.plusDays(quitDay);
        }

        //退号截止时间字符串
        String s = workDate.toString("yyyy-MM-dd") + " " + quitTime;
        Date date = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm").parseDateTime(s).toDate();
        return date;
    }

    private void packSchedule(Schedule schedule) {
        schedule.getParam().put("dayOfWeek",this.getDayOfWeek(schedule.getWorkDate()));
        schedule.getParam().put("hosname",hospitalRepository.findByHoscode(schedule.getHoscode()).getHosname());
        schedule.getParam().put("depname",departmentRepository.findByHoscodeAndDepcode(schedule.getHoscode(),schedule.getDepcode()).getDepname());
    }

    private DateTime getDateTime(Date date, String releaseTime) {
        String s = new DateTime(date).toString("yyyy-MM-dd");
        String dateTimeString = s + " " + releaseTime;
        DateTime dateTime = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm").parseDateTime(dateTimeString);
        return  dateTime;
    }

    private Integer getTotal(String hoscode, String depcode) {
        Criteria criteria = Criteria.where("hoscode").is(hoscode).and("depcode").is(depcode);

        Aggregation agg = Aggregation.newAggregation(
                Aggregation.match(criteria),
                Aggregation.group("workDate")

        );
        AggregationResults<BookingScheduleRuleVo> aggregate = mongoTemplate.aggregate(agg, Schedule.class, BookingScheduleRuleVo.class);
        List<BookingScheduleRuleVo> mappedResults = aggregate.getMappedResults();

        return mappedResults.size();
    }

    private String getDayOfWeek(Date workDate) {
        DateTime dateTime = new DateTime(workDate);
        int dayOfWeek = dateTime.getDayOfWeek();
        List<String> strings = Arrays.asList("周一", "周二", "周三", "周四", "周五", "周六", "周日");
        return strings.get(dayOfWeek-1);
    }

    private Integer sumAvailableNumber(List<Schedule> value) {
        int sum = 0;
        for (Schedule schedule: value) {
            sum += schedule.getAvailableNumber();
        }
        return sum;
    }

    private Integer sumReservedNumber(List<Schedule> value) {
        int sum = 0;
        for (Schedule schedule: value) {
            sum += schedule.getReservedNumber();
        }
        return sum;
    }


}
